#pragma once
#include <memory>
#include <sstream>
#include <stdexcept>

template<class T>
class TTable {
public:
    TTable(size_t rows, size_t columns)
        : Ptr(new T[rows * columns])
        , Rows(rows)
        , Columns(columns)
    {
    }

    TTable(size_t rows, size_t columns, const T& initValue)
        : Ptr(new T[rows * columns])
        , Rows(rows)
        , Columns(columns)
    {
        // Инициализируем спец-значениями
        for (size_t i = 0, size = Rows * Columns; i < size; ++i)
            Ptr[i] = initValue;
    }

    size_t RowsCount() const {
        return Rows;
    }

    size_t ColumnsCount() const {
        return Columns;
    }

    T& operator()(size_t i, size_t j) {
        CheckBoundaries(i, j);
        return Ptr[i * Rows + j];
    }

    const T& operator()(size_t i, size_t j) const {
        CheckBoundaries(i, j);
        return Ptr[i * Rows + j];
    }

    friend std::ostream& operator<<(std::ostream& s, const TTable& t) {
        for (size_t i = 0; i < t.Rows; ++i) {
            for (size_t j = 0; j < t.Columns; ++j) {
                s << t(i, j) << "\t";
            }
            s << "\n";
        }
        return s;
    }

private:
    void CheckBoundaries(size_t i, size_t j) const {
        if (i >= Rows || j >= Columns) {
            std::ostringstream e;
            e << "Out of table range. Table is " << Rows << "x" << Columns << ". Requested [" << i << ", " << j << "] element";
            throw std::out_of_range(e.str());
        }
    }

private:
    std::unique_ptr<T[]> Ptr;
    size_t Rows, Columns;
};
